#region Microsoft Public License
/*
 * (c) Copyright Chenjun Wu. http://utablesdk.codeplex.com
 * 
 * This source is subject to the Microsoft Public License (Ms-PL).
 * Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
 * All other rights reserved.
 */
#endregion

﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using UTable.Input;
using UTable.Input.MultiTouch;
using UTable.Objects;
using UTable.Objects.Controls;
using System.Windows.Shapes;
using System.Windows.Media;

namespace UTable.Objects
{
    /// <summary>
    /// Interaction logic for ULayoutCustomizer.xaml
    /// </summary>
    public partial class ULayoutCustomizer : UObject
    {

        #region Private Fields

		WorkspaceLayoutCalculator layoutCalculator = new WorkspaceLayoutCalculator();

        #endregion

        #region Constructor

        public ULayoutCustomizer()
        {
            InitializeComponent();
        }

        #endregion

        #region Private Methods

        private void OKButton_Pressed(object sender, ButtonEventArgs args)
        {
            UpdateLayout();
            this.Close();
        }

        private void CancelButton_Pressed(object sender, ButtonEventArgs args)
        {
            this.Close();
        }

        private void WNSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            WNText.Text = ((int)e.NewValue).ToString();
			UpdatePreview();
        }

		private void OrientationBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
		{
			LayoutDirection = (LayoutDirection)OrientationBox.SelectedIndex;
			UpdatePreview();
		}

		private void ChairmanCheck_Checked(object sender, RoutedEventArgs e)
		{
			UpdatePreview();
		}

		private void ChairmanCheck_Unchecked(object sender, RoutedEventArgs e)
		{
			UpdatePreview();
		}

		private void UpdatePreview()
		{
			if (this.IsLoaded)
			{
				this.Board.Children.Clear();
				layoutCalculator.MaxWorkspaceSize = new Size(Board.ActualWidth / 2, Board.ActualHeight / 2);
				layoutCalculator.UpdateLayout((int)this.WNSlider.Value,
											 (bool)this.ChairmanCheck.IsChecked,
											 this.LayoutDirection,
											 new Size(Board.ActualWidth, Board.ActualHeight),
											 new WorkspaceLayoutCalculator.CreateWorkspaceHandler(OnCreateWorkspacePreview));
			}
		}

		private void OnCreateWorkspacePreview(Point spacePosition, Size spaceSize, double spaceOrientation)
		{
			Rectangle workspace = new Rectangle();
			RotateTransform rotate = new RotateTransform(spaceOrientation);
			rotate.CenterX = spaceSize.Width / 2;
			rotate.CenterY = spaceSize.Height / 2;
			workspace.LayoutTransform = rotate;
			workspace.Fill = Brushes.White;
			workspace.Width = spaceSize.Width;
			workspace.Height = spaceSize.Height;
			Canvas.SetLeft(workspace, spacePosition.X);
			Canvas.SetTop(workspace, spacePosition.Y);
			if (spaceOrientation == 90 | spaceOrientation == -90)
			{
				Canvas.SetLeft(workspace, spacePosition.X + spaceSize.Height / 2);
				Canvas.SetTop(workspace, spacePosition.Y - spaceSize.Height  / 2);
			}
			Board.Children.Add(workspace);
		}

        #endregion

        #region Properties

        public LayoutDirection LayoutDirection
        {
            get { return (LayoutDirection)GetValue(LayoutDirectionProperty); }
            set { SetValue(LayoutDirectionProperty, value); }
        }

        // Using a DependencyProperty as the backing store for LayoutDirection.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty LayoutDirectionProperty =
            DependencyProperty.Register("LayoutDirection", typeof(LayoutDirection), typeof(ULayoutCustomizer), new UIPropertyMetadata(LayoutDirection.Right));


        #endregion

        #region Public Methods

        public new void UpdateLayout()
        {
            IObject tabletop = this.Owner;
            
            if (tabletop != null)
                tabletop.SendMessage(new UpdateLayoutMessage((int)this.WNSlider.Value, (bool)this.ChairmanCheck.IsChecked, this.LayoutDirection));
        }

        #endregion
    }

    public enum LayoutDirection
    {
		Left = 0,
        Up = 1,
		Right = 2,
        Down = 3
    }

    internal class UpdateLayoutMessage
    {
        public UpdateLayoutMessage(int worksapceNumber, bool hasChairman, LayoutDirection direction)
        {
            this.WorkspaceNumber = worksapceNumber;
            this.HasChairman = hasChairman;
            this.Direction = direction;
        }

        public int WorkspaceNumber { get; set; }

        public bool HasChairman { get; set; }

        public LayoutDirection Direction { get; set; }
    }

	internal class WorkspaceLayoutCalculator
	{
		public Size MaxWorkspaceSize = new Size(780, 500);

		public delegate void CreateWorkspaceHandler(Point spacePosition, Size spaceSize, double spaceOrientation);

		/// <summary>
        /// Update the layout of the tabletop according to the given parameters
        /// </summary>
        public void UpdateLayout(int workspaceNumber, bool hasChariman, LayoutDirection direction, Size wholeSize, CreateWorkspaceHandler createHandler)
        {
            // Firstly clear the tabletop

            if (workspaceNumber <= 0)
                return;

            // calculate the bound of each workspace
            Point[] boundPositions = new Point[workspaceNumber];
            Size[] boundSizes = new Size[workspaceNumber];
            LayoutDirection[] boundDirections = new LayoutDirection[workspaceNumber];

            double tabletopHeight, tabeltopWidth;
            if (direction == LayoutDirection.Up || direction == LayoutDirection.Down)
            {
				tabeltopWidth = wholeSize.Width;
                tabletopHeight = wholeSize.Height;
            }
            else
            {
                tabeltopWidth = wholeSize.Height;
                tabletopHeight = wholeSize.Width;
            }

            double boundWidth, boundHeight, widthInterval;

            if (hasChariman)
            {
                double a = MaxWorkspaceSize.Height / MaxWorkspaceSize.Width;
                double n = workspaceNumber / 2;
                boundWidth = tabletopHeight / (n + a);
                boundHeight = tabletopHeight - n * boundWidth;
                widthInterval = tabeltopWidth - 2 * boundHeight;
                if (boundHeight * 2 > tabeltopWidth)
                {
                    boundHeight = tabeltopWidth / 2;
                    widthInterval = 0;
                }

                // set the top space
                boundPositions[0] = new Point(0, 0);
                boundSizes[0] = new Size(boundWidth, boundHeight);
                boundDirections[0] = LayoutDirection.Down;


                LayoutDirection d;
                double positionX, positionY;
                int index;

                // set the left spaces
                d = LayoutDirection.Right;
                positionX = 0;
                positionY = boundSizes[0].Height;
                for (index = 1; index < workspaceNumber / 2 + 1; index++)
                {
                    boundPositions[index] = new Point(positionX, positionY);
                    boundSizes[index] = new Size(boundHeight, boundWidth);
                    boundDirections[index] = d;
                    positionY += boundWidth;
                }

                // set the right spaces
                positionX = boundHeight + widthInterval;
                positionY = boundSizes[0].Height;
                d = LayoutDirection.Left;
                for (; index < workspaceNumber; index++)
                {
                    boundPositions[index] = new Point(positionX, positionY);
                    boundSizes[index] = new Size(boundHeight, boundWidth);
                    boundDirections[index] = d;
                    positionY += boundWidth;
                }

            }
            else
            {
                boundWidth = tabletopHeight / ((workspaceNumber + 1) / 2);
				if (workspaceNumber == 1)
					boundHeight = tabeltopWidth;
				else
					boundHeight = tabeltopWidth / 2;
                widthInterval = 0;
                if (boundHeight / boundWidth > MaxWorkspaceSize.Height / MaxWorkspaceSize.Width)
                {
                    boundHeight = boundWidth * MaxWorkspaceSize.Height / MaxWorkspaceSize.Width;
                    widthInterval = tabeltopWidth - boundHeight * 2;
                }

                LayoutDirection d;
                double positionX, positionY;
                int index;
                
                // set the left spaces
                d = LayoutDirection.Right;
                positionX = 0;
                positionY = 0;
                for (index = 0; index < (workspaceNumber + 1) / 2; index++)
                {
                    boundPositions[index] = new Point(positionX, positionY);
                    boundSizes[index] = new Size(boundHeight, boundWidth);
                    boundDirections[index] = d;
                    positionY += boundWidth;
                }

                // set the right spaces
                positionX = boundHeight + widthInterval;
                positionY = 0;
                d = LayoutDirection.Left;
                for (; index < workspaceNumber; index++)
                {
                    boundPositions[index] = new Point(positionX, positionY);
                    boundSizes[index] = new Size(boundHeight, boundWidth);
                    boundDirections[index] = d;
                    positionY += boundWidth;
                }
            }

            // now adjust the positions and sizes according to the direction
            switch (direction)
            {
                case LayoutDirection.Up:
                    // do nothing
                    break;
                case LayoutDirection.Down:
                    if (hasChariman)
                    {
                        boundDirections[0] = LayoutDirection.Up;
                        for (int i = 0; i<workspaceNumber; i++)
                        {
                            boundPositions[i] = new Point(boundPositions[i].X, tabletopHeight - boundPositions[i].Y - boundSizes[i].Height);
                        }
                    }
                    else
                    {
                        // do nothing
                    }
                    break;
                case LayoutDirection.Left:
                    for (int i = 0; i < workspaceNumber; i++)
                    {
                        boundSizes[i] = new Size(boundSizes[i].Height, boundSizes[i].Width);
                        boundPositions[i] = new Point(boundPositions[i].Y, wholeSize.Height - boundPositions[i].X - boundSizes[i].Height);
                        switch (boundDirections[i])
                        {
                            case LayoutDirection.Left:
                                boundDirections[i] = LayoutDirection.Down;
                                break;
                            case LayoutDirection.Down:
                                boundDirections[i] = LayoutDirection.Right;
                                break;
                            case LayoutDirection.Up:
                                boundDirections[i] = LayoutDirection.Left;
                                break;
                            case LayoutDirection.Right:
                                boundDirections[i] = LayoutDirection.Up;
                                break;
                        }
                    }
                    if (hasChariman)
                    {
						boundPositions[0] = new Point(boundPositions[0].X, wholeSize.Height - boundPositions[0].Y - boundSizes[0].Height);
                    }
                    break;
                case LayoutDirection.Right:
                    for (int i = 0; i < workspaceNumber; i++)
                    {
                        boundSizes[i] = new Size(boundSizes[i].Height, boundSizes[i].Width);
						boundPositions[i] = new Point(wholeSize.Width - boundPositions[i].Y - boundSizes[i].Width, boundPositions[i].X);
                        switch (boundDirections[i])
                        {
                            case LayoutDirection.Left:
                                boundDirections[i] = LayoutDirection.Up;
                                break;
                            case LayoutDirection.Down:
                                boundDirections[i] = LayoutDirection.Left;
                                break;
                            case LayoutDirection.Up:
                                boundDirections[i] = LayoutDirection.Right;
                                break;
                            case LayoutDirection.Right:
                                boundDirections[i] = LayoutDirection.Down;
                                break;
                        }
                    }
                    break;
            }

            // create the workspaces and put them on the tabletop
            for (int i=0; i<workspaceNumber; i++)
            {
				Point spacePosition;
				Size spaceSize;
				double spaceOrientation;
				CalculateSingleWorkspace(boundPositions[i], boundSizes[i], boundDirections[i],
					out spacePosition, out spaceSize, out spaceOrientation);

				createHandler.Invoke(spacePosition, spaceSize, spaceOrientation);

                // Console.WriteLine("Position: " + spacePosition);
                // Console.WriteLine("Size: " + spaceSize);
                // Console.WriteLine("SpaceOrientation: " + spaceOrientation);
            }
        }

        private void CalculateSingleWorkspace(Point boundPosition, Size boundSize, LayoutDirection direction, out Point spacePosition, out Size spaceSize, out double orientation)
        {
            // define the default intervals
            double topInterval = 10;
            double leftInterval = 10;
            double rightInterval = 10;
            double bottomInterval = 5;

            // foolish of the VS!
            spacePosition = new Point();
            spaceSize = new Size();
            orientation = 0;

            // modify the intervals to fit the constraint
            // firstly assume the direction is UP
            Size bSize;
            if (direction == LayoutDirection.Down || direction == LayoutDirection.Up)
            {
                bSize = boundSize;
            }
            else
            {
                bSize = new Size(boundSize.Height, boundSize.Width);
            }
            // determine the size of the space
            double availableWidth = bSize.Width - leftInterval - rightInterval;
            double availableHeight = bSize.Height - topInterval - bottomInterval;
            if (availableHeight < MaxWorkspaceSize.Height || availableWidth < MaxWorkspaceSize.Width)
            {
                if (availableHeight / availableWidth > MaxWorkspaceSize.Height / MaxWorkspaceSize.Width)
                {
                    spaceSize = new Size(availableWidth, availableWidth / MaxWorkspaceSize.Width * MaxWorkspaceSize.Height);
                    topInterval = bSize.Height - spaceSize.Height - bottomInterval;
                }
                else
                {
                    spaceSize = new Size(availableHeight / MaxWorkspaceSize.Height * MaxWorkspaceSize.Width, availableHeight);
                    leftInterval = rightInterval = (bSize.Width - spaceSize.Width) / 2;
                }
            }
            else
            {
                spaceSize = MaxWorkspaceSize;
                topInterval = bSize.Height - spaceSize.Height - bottomInterval;
                leftInterval = rightInterval = (bSize.Width - spaceSize.Width) / 2;
            }

            // determine the spacePosition and direction
            // the rotate center is at the center of the object
            switch (direction)
            {
                case LayoutDirection.Up:
                    orientation = 0;
                    spacePosition = new Point(boundPosition.X + leftInterval, boundPosition.Y + topInterval);
                    break;
                case LayoutDirection.Down:
                    orientation = 180;
                    spacePosition = new Point(boundPosition.X + rightInterval, boundPosition.Y + bottomInterval);
                    break;
                case LayoutDirection.Left:
                    orientation = -90;
                    spacePosition = new Point(boundPosition.X + topInterval + spaceSize.Height / 2 - spaceSize.Width / 2, boundPosition.Y + rightInterval + spaceSize.Width / 2 - spaceSize.Height / 2);
                    break;
                case LayoutDirection.Right:
                    orientation = 90;
                    spacePosition = new Point(boundPosition.X + bottomInterval + spaceSize.Height / 2 - spaceSize.Width / 2, boundPosition.Y + leftInterval + spaceSize.Width / 2 - spaceSize.Height / 2);
                    break;
            }

        }
	}
}
